﻿using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using NRails;
using NRails.Spark;
using Spark;
using Spark.Compiler;
using Spark.Parser.Code;

namespace NRails.Tests.Spark
{
    public static class SparkViewExtensions
    {
        public static RenderView(this view : ISparkView) : string 
        {
            def writer = StringWriter();
            view.RenderView(writer);
            writer.ToString();
        }

    }
    
    [TestFixture]
    public class NemerleViewCompilerTester
    {

        [SetUp]
        public Init() : void
        {
        }

        private static DoCompileView(compiler : ViewCompiler , chunks : IList[Chunk]) : void 
        {
            compiler.CompileView([chunks], [chunks]);
        }
        
        NewChunk[T](init : T -> void) : T 
            where T : new()
        {
            def res = T();
            init(res);
            res
        }

        [Test]
        public MakeAndCompile() : void
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "Spark.SparkViewBase";

            DoCompileView(compiler, array[ NewChunk.[SendLiteralChunk](c => { c.Text = "hello world" }) ]);

            def instance = compiler.CreateInstance();
            def contents = instance.RenderView();

            Assert.That(contents.Contains("hello world"));
        }


        [Test]
        public UnsafeLiteralCharacters() : void
        {
            def text = "hello\t\r\n\"world";
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "Spark.SparkViewBase";
            DoCompileView(compiler, array[ NewChunk.[SendLiteralChunk](c => c.Text = text)]);

            Assert.That(compiler.SourceCode.Contains("Write(\"hello\\t\\r\\n\\\"world\")"));

            def instance = compiler.CreateInstance();
            def contents = instance.RenderView();

            Assert.AreEqual(text, contents);
        }

        [Test]
        public SimpleOutput() : void 
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "Spark.SparkViewBase";
            DoCompileView(compiler, array[ NewChunk.[SendExpressionChunk](c => c.Code = ("3 + 4" :> Snippets) ) ]);
            def instance = compiler.CreateInstance();
            def contents = instance.RenderView();
            Assert.AreEqual("7", contents);
        }

        [Test]
        public LocalVariableDecl() : void 
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "Spark.SparkViewBase";
            DoCompileView(compiler, array[
                                        NewChunk.[LocalVariableChunk](c => { c.Name = ("i" :> Snippets); c.Value = ("5" :> Snippets) }), 
                                        NewChunk.[SendExpressionChunk](c=> c.Code = ("i" :> Snippets))
                                    ]);
            def instance = compiler.CreateInstance();
            def contents = instance.RenderView();

            Assert.AreEqual("5", contents);
        }
        
        LocalVariable(Name : string, Value : string) : LocalVariableChunk 
        {
            NewChunk.[LocalVariableChunk](c => {c.Type = "def" : Snippets; c.Name = Name :> Snippets;c.Value = Value :> Snippets})
        }
        GlobalVariable(Name : string, Value : string) : GlobalVariableChunk 
        {
            NewChunk.[GlobalVariableChunk](c => {c.Name = Name :> Snippets;c.Value = Value :> Snippets})
        }
        AssignVariable(Name : string, Value : string) : AssignVariableChunk 
        {
            NewChunk.[AssignVariableChunk](c => {c.Name = Name;c.Value = Value :> Snippets})
        }
        SendLiteral(Text : string) : SendLiteralChunk 
        {
            NewChunk.[SendLiteralChunk](c => {c.Text = Text})
        }
        SendExpression(Code : string) : SendExpressionChunk 
        {
            NewChunk.[SendExpressionChunk](c => {c.Code = Code :> Snippets})
        }
        ForEach(Code : string, Body : array[Chunk]) : ForEachChunk 
        {
            NewChunk.[ForEachChunk](c => {c.Code = Code :> Snippets; c.Body = Body})
        }
        Conditional(Type : ConditionalType, Condition : string, Body : array[Chunk]) : ConditionalChunk 
        {
            NewChunk.[ConditionalChunk](c => {c.Type = Type; c.Condition = Condition :> Snippets; c.Body = Body})
        }

        [Test]
        public ForEachLoop() : void
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "Spark.SparkViewBase";
            DoCompileView(compiler, array
                                    [
                                        LocalVariable(Name = "data", Value = "array[3,4,5]"),
                                        SendLiteral(Text = "<ul>"),
                                        ForEach
                                        (
                                            Code = "item in data",
                                            Body = array
                                                   [ 
                                                       SendLiteral(Text = "<li>"),
                                                       SendExpression(Code = "item"),
                                                       SendLiteral(Text = "</li>")
                                                   ]
                                        ),
                                        SendLiteral(Text = "</ul>")
                                    ]);
            def instance = compiler.CreateInstance();
            def contents = instance.RenderView();
            Assert.AreEqual("<ul><li>3</li><li>4</li><li>5</li></ul>", contents);
        }

        [Test]
        public GlobalVariables() : void 
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "Spark.SparkViewBase";
            DoCompileView(compiler, array
                                    [
                                        SendExpression(Code="title"),
                                        AssignVariable(Name="item", Value="8"),
                                        SendLiteral(Text=":"),
                                        SendExpression(Code="item"),
                                        GlobalVariable(Name="title", Value="\"hello world\""),
                                        GlobalVariable(Name="item", Value="3")
                                    ]);
            def instance = compiler.CreateInstance();
            def contents = instance.RenderView();
            Assert.AreEqual("hello world:8", contents);
        }

        [Test]
        public TargetNamespace(): void 
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "Spark.SparkViewBase";
            compiler.Descriptor = SparkViewDescriptor();
            compiler.Descriptor.TargetNamespace = "Testing.Target.Namespace";

            DoCompileView(compiler, array[SendLiteral(Text = "Hello") ]);
            def instance = compiler.CreateInstance();
            Assert.AreEqual("Testing.Target.Namespace", instance.GetType().Namespace);

        }


        [Test, ExpectedException(typeof(CompilerException))]
        public ProvideFullException() : void 
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "Spark.SparkViewBase";
            DoCompileView(compiler, array[SendExpression(Code = "NoSuchVariable")]);
        }

        [Test]
        public IfTrueCondition() : void
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "Spark.SparkViewBase";

            def trueChunks = array[SendLiteral(Text = "wastrue")];

            DoCompileView(compiler, array
                                    [
                                        SendLiteral(Text = "<p>"),
                                        LocalVariable(Name="arg", Value="5"),
                                        Conditional(Type=ConditionalType.If, Condition="arg==5", Body=trueChunks),
                                        SendLiteral(Text = "</p>")
                                    ]);
            def instance = compiler.CreateInstance();
            def contents = instance.RenderView();
            Assert.AreEqual("<p>wastrue</p>", contents);
        }

        [Test]
        public IfFalseCondition() : void
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "Spark.SparkViewBase";

            def trueChunks = array[SendLiteral(Text = "wastrue")];

            DoCompileView(compiler, array
                                    [
                                        SendLiteral(Text = "<p>"),
                                        LocalVariable(Name="arg", Value="5"),
                                        Conditional(Type=ConditionalType.If, Condition="arg==6", Body=trueChunks),
                                        SendLiteral(Text = "</p>")
                                    ]);
            def instance = compiler.CreateInstance();
            def contents = instance.RenderView();
            Assert.AreEqual("<p></p>", contents);
        }

        [Test]
        public IfElseFalseCondition() : void
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "Spark.SparkViewBase";

            def trueChunks = array[SendLiteral(Text = "wastrue")];
            def falseChunks = array[SendLiteral(Text = "wasfalse")];

            DoCompileView(compiler, array
                                    [
                                        SendLiteral(Text = "<p>"),
                                        LocalVariable(Name="arg", Value="5"),
                                        Conditional(Type=ConditionalType.If, Condition="arg==6", Body=trueChunks),
                                        Conditional(Type=ConditionalType.Else, Body=falseChunks, Condition=null),
                                        SendLiteral(Text = "</p>")
                                    ]);
            def instance = compiler.CreateInstance();
            def contents = instance.RenderView();
            Assert.AreEqual("<p>wasfalse</p>", contents);
        }

        [Test]
        public LenientSilentNullDoesNotCauseWarningCS0168() : void
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "NRails.Tests.Spark.StubSparkView";
            compiler.NullBehaviour = NullBehaviour.Lenient;

            def chunks = array
                         [
                             NewChunk.[ViewDataChunk](c => { c.Name="comment" :> Snippets; c.Type="NRails.Tests.Spark.Comment" :> Snippets}),
                             NewChunk.[SendExpressionChunk](c => {c.Code = "comment.Text" :> Snippets; c.SilentNulls = true})
                         ];
            compiler.CompileView([chunks], [chunks]);
            Assert.That(compiler.SourceCode.Contains("catch"));
            Assert.That(compiler.SourceCode.Contains("System.NullReferenceException"));
        }

        [Test]
        public LenientOutputNullDoesNotCauseWarningCS0168() : void
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "NRails.Tests.Spark.StubSparkView";
            compiler.NullBehaviour = NullBehaviour.Lenient;

            def chunks = array
                         [
                             NewChunk.[ViewDataChunk](c => { c.Name="comment" :> Snippets; c.Type="NRails.Tests.Spark.Comment" :> Snippets}),
                             NewChunk.[SendExpressionChunk](c => {c.Code = "comment.Text" :> Snippets; c.SilentNulls = false})
                         ];
            compiler.CompileView([chunks], [chunks]);
            Assert.That(compiler.SourceCode.Contains("catch"));
            Assert.That(compiler.SourceCode.Contains("System.NullReferenceException"));
        }

        [Test]
        public StrictNullUsesException() : void
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "NRails.Tests.Spark.StubSparkView";
            compiler.NullBehaviour = NullBehaviour.Strict;
            

            def chunks = array
                         [
                             NewChunk.[ViewDataChunk](c => { c.Name="comment" :> Snippets; c.Type="NRails.Tests.Spark.Comment" :> Snippets}),
                             NewChunk.[SendExpressionChunk](c => {c.Code = "comment.Text" :> Snippets; c.SilentNulls = false})
                         ];
                         
            compiler.CompileView([chunks], [chunks]);
            Assert.That(compiler.SourceCode.Contains("catch"));
            Assert.That(compiler.SourceCode.Contains("ex is System.NullReferenceException"));
            Assert.That(compiler.SourceCode.Contains("ArgumentNullException("));
            Assert.That(compiler.SourceCode.Contains(", ex);"));
        }

        [Test]
        public PageBaseTypeOverridesBaseClass() : void
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "NRails.Tests.Spark.StubSparkView";
            compiler.NullBehaviour = NullBehaviour.Strict;
            
            DoCompileView(compiler, array
                                    [
                                        NewChunk.[PageBaseTypeChunk](c => c.BaseClass="NRails.Tests.Spark.StubSparkView2" : Snippets),
                                        NewChunk.[SendLiteralChunk](c =>{ c.Text = "Hello world"})
                                    ]);
            def instance = compiler.CreateInstance();
            Assert.That(instance, Is.InstanceOf.[StubSparkView2]());
        }


        [Test]
        public PageBaseTypeWorksWithOptionalModel() : void
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "NRails.Tests.Spark.StubSparkView";
            compiler.NullBehaviour = NullBehaviour.Strict;

            DoCompileView(compiler, array
                                    [
                                        NewChunk.[PageBaseTypeChunk](c => c.BaseClass="NRails.Tests.Spark.StubSparkView2" : Snippets),
                                        NewChunk.[ViewDataModelChunk](c => {c.TModel = "NRails.Tests.Spark.Comment" : Snippets}),
                                        NewChunk.[SendLiteralChunk](c =>{ c.Text = "Hello world"})
                                    ]);
            def instance = compiler.CreateInstance();
            Assert.That(instance, Is.InstanceOf.[StubSparkView2]());
            Assert.That(instance, Is.InstanceOf.[StubSparkView2[Comment]]());
        }

        [Test]
        public PageBaseTypeWorksWithGenericParametersIncluded() : void
        {
            def compiler = NemerleViewCompiler();
            compiler.BaseClass = "NRails.Tests.Spark.StubSparkView";
            compiler.NullBehaviour = NullBehaviour.Strict;

            DoCompileView(compiler, array
                                    [
                                        NewChunk.[PageBaseTypeChunk](c => c.BaseClass = "NRails.Tests.Spark.StubSparkView3[NRails.Tests.Spark.Comment, string]" : Snippets),
                                        NewChunk.[SendLiteralChunk](c =>{ c.Text = "Hello world"})
                                    ]);
            def instance = compiler.CreateInstance();
            Assert.That(instance, Is.InstanceOf.[StubSparkView2]());
            Assert.That(instance, Is.InstanceOf.[StubSparkView2[Comment]]());
            Assert.That(instance, Is.InstanceOf.[StubSparkView3[Comment, string]]());
        }
    }
}
